Interactive graphics with plotly
install.packages("plotly")
First Interactive Plot
library(plotly)
library(tidyverse)
p <- ggplot(data = midwest) +
geom_point(mapping = aes(x = popdensity, y = percollege))
ggplotly(p)
Customized Interactive Plot
p <- ggplot(midwest,
aes(x = popdensity, y = percollege, color = state)) +
geom_point() +
scale_x_continuous("Population Density",
breaks = seq(0, 80000, 20000)) +
scale_y_continuous("Percent College Graduates") +
scale_color_discrete("State") +
theme_bw()
ggplotly(p)
Your Turn
- Using the
starwars data, create a static ggplot and use the ggplotly function to turn it interactive.
Lord of the Rings Data
lotr <- read_tsv('https://raw.githubusercontent.com/jennybc/lotr/master/lotr_clean.tsv')
Parsed with column specification:
cols(
Film = col_character(),
Chapter = col_character(),
Character = col_character(),
Race = col_character(),
Words = col_integer()
)
lotr
Create plotly by hand
plot_ly(lotr, x = ~Words) %>% add_histogram()
Subplots
one_plot <- function(d) {
plot_ly(d, x = ~Words) %>%
add_histogram() %>%
add_annotations(
~unique(Film), x = 0.5, y = 1,
xref = "paper", yref = "paper", showarrow = FALSE
)
}
lotr %>%
split(.$Film) %>%
lapply(one_plot) %>%
subplot(nrows = 1, shareX = TRUE, titleX = FALSE) %>%
hide_legend()
Grouped bar plot
plot_ly(lotr, x = ~Race, color = ~Film) %>% add_histogram()
Plot of proportions
# number of diamonds by cut and clarity (n)
lotr_count <- count(lotr, Race, Film)
# number of diamonds by cut (nn)
lotr_prop <- left_join(lotr_count, count(lotr_count, Race, wt = n))
lotr_prop %>%
mutate(prop = n / nn) %>%
plot_ly(x = ~Race, y = ~prop, color = ~Film) %>%
add_bars() %>%
layout(barmode = "stack")
Your Turn
- Using the
gss_cat data, create a histrogram for the tvhours variable.
- Using the
gss_cat data, create a bar chart showing the partyid variable by the marital status.
Scatterplots by Hand
plot_ly(midwest, x = ~popdensity, y = ~percollege) %>%
add_markers()
Change symbol
plot_ly(midwest, x = ~popdensity, y = ~percollege) %>%
add_markers(symbol = ~state)
Change color
plot_ly(midwest, x = ~popdensity, y = ~percollege) %>%
add_markers(color = ~state, colors = viridis::viridis(5))
Line Graph
storms_yearly <- storms %>%
group_by(year) %>%
summarise(num = length(unique(name)))
plot_ly(storms_yearly, x = ~year, y = ~num) %>%
add_lines()
Your Turn
- Using the
gss_cat data, create a scatterplot showing the age and tvhours variables.
- Compute the average time spent watching tv by year and marital status. Then, plot the average time spent watching tv by year and marital status.
Highcharter; Highcharts for R
devtools::install_github("jbkunst/highcharter")
hchart function
library(highcharter)
Highcharts (www.highcharts.com) is a Highsoft software product which is
not free for commercial and Governmental use
lotr_count <- lotr %>%
count(Film, Race)
hchart(lotr_count, "column", hcaes(x = Race, y = n, group = Film))
A second hchart
hchart(midwest, "scatter", hcaes(x = popdensity, y = percollege, group = state))
Histogram
hchart(lotr$Words)
Your Turn
- Using the
hchart function, create a bar chart or histogram with the gss_cat data.
- Using the
hchart function, create a scatterplot with the gss_cat data.
Build Highcharts from scratch
hc <- highchart() %>%
hc_xAxis(categories = lotr_count$Race) %>%
hc_add_series(name = 'The Fellowship Of The Ring',
data = filter(lotr_count, Film == 'The Fellowship Of The Ring')$n) %>%
hc_add_series(name = 'The Two Towers',
data = filter(lotr_count, Film == 'The Two Towers')$n) %>%
hc_add_series(name = 'The Return Of The King',
data = filter(lotr_count, Film == 'The Return Of The King')$n)
hc
Change Chart type
hc <- hc %>%
hc_chart(type = 'column')
hc
Change Colors
hc <- hc %>%
hc_colors(substr(viridis(3), 0, 7))
hc
Modify Axes
hc <- hc %>%
hc_xAxis(title = list(text = "Race")) %>%
hc_yAxis(title = list(text = "Number of Words Spoken"),
showLastLabel = FALSE)
hc
Add title, subtitle, move legend
hc <- hc %>%
hc_title(text = 'Number of Words Spoken in Lord of the Rings Films',
align = 'left') %>%
hc_subtitle(text = 'Broken down by <i>Film</i> and <b>Race</b>',
align = 'left') %>%
hc_legend(align = 'right', verticalAlign = 'top', layout = 'vertical',
x = 0, y = 80) %>%
hc_exporting(enabled = TRUE)
hc
Your Turn
- Build up a plot from scratch, getting the figure close to publication quality using the
gss_cat data.
Correlation Matrices
select(storms, wind, pressure, ts_diameter, hu_diameter) %>%
cor(use = "pairwise.complete.obs") %>%
hchart()
Leaflet Example
library(leaflet)
storms %>%
filter(name %in% c('Ike', 'Katrina'), year > 2000) %>%
leaflet() %>%
addTiles() %>%
addCircles(lng = ~long, lat = ~lat, popup = ~name, weight = 1,
radius = ~wind*1000)
LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiAtIEludGVyYWN0aXZlIEdyYXBoaWNzIHVzaW5nIFIiDQphdXRob3I6ICJCcmFuZG9uIExlQmVhdSINCmRhdGU6ICJKdW5lIDUsIDIwMTgiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBzZXR1cF9jaHVua3MsIGVjaG8gPSBGQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NiwgZmlnLmNhcCA9IE5VTEwpIA0KYGBgDQoNCiMgSW50ZXJhY3RpdmUgZ3JhcGhpY3Mgd2l0aCBwbG90bHkNCmBgYHtyIGluc3RhbGwsIGV2YWwgPSBGQUxTRX0NCmluc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQpgYGANCg0KIyBGaXJzdCBJbnRlcmFjdGl2ZSBQbG90DQpgYGB7ciBmaXJzdF9wbG90bHksIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDZ9DQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IG1pZHdlc3QpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBwb3BkZW5zaXR5LCB5ID0gcGVyY29sbGVnZSkpDQpnZ3Bsb3RseShwKQ0KYGBgDQoNCiMgQ3VzdG9taXplZCBJbnRlcmFjdGl2ZSBQbG90DQpgYGB7ciBjdXN0b21fcGxvdGx5LCB3YXJuaW5nID0gRkFMU0UsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA2fQ0KcCA8LSBnZ3Bsb3QobWlkd2VzdCwgDQogICAgICAgYWVzKHggPSBwb3BkZW5zaXR5LCB5ID0gcGVyY29sbGVnZSwgY29sb3IgPSBzdGF0ZSkpICsNCiAgZ2VvbV9wb2ludCgpICsgDQogIHNjYWxlX3hfY29udGludW91cygiUG9wdWxhdGlvbiBEZW5zaXR5IiwgDQogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgODAwMDAsIDIwMDAwKSkgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKCJQZXJjZW50IENvbGxlZ2UgR3JhZHVhdGVzIikgKyANCiAgc2NhbGVfY29sb3JfZGlzY3JldGUoIlN0YXRlIikgKyANCiAgdGhlbWVfYncoKQ0KZ2dwbG90bHkocCkNCmBgYA0KDQojIFlvdXIgVHVybg0KMS4gVXNpbmcgdGhlIGBzdGFyd2Fyc2AgZGF0YSwgY3JlYXRlIGEgc3RhdGljIGdncGxvdCBhbmQgdXNlIHRoZSBgZ2dwbG90bHlgIGZ1bmN0aW9uIHRvIHR1cm4gaXQgaW50ZXJhY3RpdmUuIA0KDQojIExvcmQgb2YgdGhlIFJpbmdzIERhdGENCi0gRGF0YSBmcm9tIEplbm55IEJyeWFuOiA8aHR0cHM6Ly9naXRodWIuY29tL2plbm55YmMvbG90cj4NCg0KYGBge3IgcmVhZF9pbl9sb3RyLCBlcnJvciA9IEZBTFNFfQ0KbG90ciA8LSByZWFkX3RzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2plbm55YmMvbG90ci9tYXN0ZXIvbG90cl9jbGVhbi50c3YnKQ0KbG90cg0KYGBgDQoNCiMgQ3JlYXRlIHBsb3RseSBieSBoYW5kDQpgYGB7ciBwbG90bHlfYnlfaGFuZCwgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDZ9DQpwbG90X2x5KGxvdHIsIHggPSB+V29yZHMpICU+JSBhZGRfaGlzdG9ncmFtKCkNCmBgYA0KDQojIFN1YnBsb3RzDQpgYGB7ciBzdWJwbG90cywgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDZ9DQpvbmVfcGxvdCA8LSBmdW5jdGlvbihkKSB7DQogIHBsb3RfbHkoZCwgeCA9IH5Xb3JkcykgJT4lDQogICAgYWRkX2hpc3RvZ3JhbSgpICU+JQ0KICAgIGFkZF9hbm5vdGF0aW9ucygNCiAgICAgIH51bmlxdWUoRmlsbSksIHggPSAwLjUsIHkgPSAxLCANCiAgICAgIHhyZWYgPSAicGFwZXIiLCB5cmVmID0gInBhcGVyIiwgc2hvd2Fycm93ID0gRkFMU0UNCiAgICApDQp9DQoNCmxvdHIgJT4lDQogIHNwbGl0KC4kRmlsbSkgJT4lDQogIGxhcHBseShvbmVfcGxvdCkgJT4lIA0KICBzdWJwbG90KG5yb3dzID0gMSwgc2hhcmVYID0gVFJVRSwgdGl0bGVYID0gRkFMU0UpICU+JQ0KICBoaWRlX2xlZ2VuZCgpDQpgYGANCg0KDQojIEdyb3VwZWQgYmFyIHBsb3QNCmBgYHtyIHBsb3RseV9ncm91cCwgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDZ9DQpwbG90X2x5KGxvdHIsIHggPSB+UmFjZSwgY29sb3IgPSB+RmlsbSkgJT4lIGFkZF9oaXN0b2dyYW0oKQ0KYGBgDQoNCiMgUGxvdCBvZiBwcm9wb3J0aW9ucw0KYGBge3IgcGxvdGx5X3Byb3BvcnRpb25zLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA2fQ0KIyBudW1iZXIgb2YgZGlhbW9uZHMgYnkgY3V0IGFuZCBjbGFyaXR5IChuKQ0KbG90cl9jb3VudCA8LSBjb3VudChsb3RyLCBSYWNlLCBGaWxtKQ0KIyBudW1iZXIgb2YgZGlhbW9uZHMgYnkgY3V0IChubikNCmxvdHJfcHJvcCA8LSBsZWZ0X2pvaW4obG90cl9jb3VudCwgY291bnQobG90cl9jb3VudCwgUmFjZSwgd3QgPSBuKSkNCmxvdHJfcHJvcCAlPiUNCiAgbXV0YXRlKHByb3AgPSBuIC8gbm4pICU+JQ0KICBwbG90X2x5KHggPSB+UmFjZSwgeSA9IH5wcm9wLCBjb2xvciA9IH5GaWxtKSAlPiUNCiAgYWRkX2JhcnMoKSAlPiUNCiAgbGF5b3V0KGJhcm1vZGUgPSAic3RhY2siKQ0KYGBgDQoNCiMgWW91ciBUdXJuDQoxLiBVc2luZyB0aGUgYGdzc19jYXRgIGRhdGEsIGNyZWF0ZSBhIGhpc3Ryb2dyYW0gZm9yIHRoZSBgdHZob3Vyc2AgdmFyaWFibGUuIA0KMi4gVXNpbmcgdGhlIGBnc3NfY2F0YCBkYXRhLCBjcmVhdGUgYSBiYXIgY2hhcnQgc2hvd2luZyB0aGUgYHBhcnR5aWRgIHZhcmlhYmxlIGJ5IHRoZSBgbWFyaXRhbGAgc3RhdHVzLg0KDQojIFNjYXR0ZXJwbG90cyBieSBIYW5kDQpgYGB7ciBwbG90bHlfc2NhdHRlciwgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDYsIHdhcm5pbmcgPSBGQUxTRX0NCnBsb3RfbHkobWlkd2VzdCwgeCA9IH5wb3BkZW5zaXR5LCB5ID0gfnBlcmNvbGxlZ2UpICU+JQ0KICBhZGRfbWFya2VycygpDQpgYGANCg0KIyBDaGFuZ2Ugc3ltYm9sDQpgYGB7ciBwbG90bHlfc3ltYm9sLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNn0NCnBsb3RfbHkobWlkd2VzdCwgeCA9IH5wb3BkZW5zaXR5LCB5ID0gfnBlcmNvbGxlZ2UpICU+JQ0KICBhZGRfbWFya2VycyhzeW1ib2wgPSB+c3RhdGUpDQpgYGANCg0KIyBDaGFuZ2UgY29sb3INCmBgYHtyIHBsb3RseV9jb2xvciwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDl9DQpwbG90X2x5KG1pZHdlc3QsIHggPSB+cG9wZGVuc2l0eSwgeSA9IH5wZXJjb2xsZWdlKSAlPiUNCiAgYWRkX21hcmtlcnMoY29sb3IgPSB+c3RhdGUsIGNvbG9ycyA9IHZpcmlkaXM6OnZpcmlkaXMoNSkpDQpgYGANCg0KIyBMaW5lIEdyYXBoDQpgYGB7ciBwbG90bHlfbGluZSwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDl9DQpzdG9ybXNfeWVhcmx5IDwtIHN0b3JtcyAlPiUNCiAgZ3JvdXBfYnkoeWVhcikgJT4lDQogIHN1bW1hcmlzZShudW0gPSBsZW5ndGgodW5pcXVlKG5hbWUpKSkNCg0KcGxvdF9seShzdG9ybXNfeWVhcmx5LCB4ID0gfnllYXIsIHkgPSB+bnVtKSAlPiUNCiAgYWRkX2xpbmVzKCkNCmBgYA0KDQojIFlvdXIgVHVybg0KMS4gVXNpbmcgdGhlIGBnc3NfY2F0YCBkYXRhLCBjcmVhdGUgYSBzY2F0dGVycGxvdCBzaG93aW5nIHRoZSBgYWdlYCBhbmQgYHR2aG91cnNgIHZhcmlhYmxlcy4NCjIuIENvbXB1dGUgdGhlIGF2ZXJhZ2UgdGltZSBzcGVudCB3YXRjaGluZyB0diBieSB5ZWFyIGFuZCBtYXJpdGFsIHN0YXR1cy4gVGhlbiwgcGxvdCB0aGUgYXZlcmFnZSB0aW1lIHNwZW50IHdhdGNoaW5nIHR2IGJ5IHllYXIgYW5kIG1hcml0YWwgc3RhdHVzLg0KDQojIEhpZ2hjaGFydGVyOyBIaWdoY2hhcnRzIGZvciBSDQpgYGB7ciBpbnN0YWxsX2hpZ2hjaGFydGVyLCBldmFsID0gRkFMU0V9DQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImpia3Vuc3QvaGlnaGNoYXJ0ZXIiKQ0KYGBgDQoNCiMgYGhjaGFydGAgZnVuY3Rpb24NCmBgYHtyIGhjaGFydDEsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA5fQ0KbGlicmFyeShoaWdoY2hhcnRlcikNCg0KbG90cl9jb3VudCA8LSBsb3RyICU+JQ0KICBjb3VudChGaWxtLCBSYWNlKQ0KDQpoY2hhcnQobG90cl9jb3VudCwgImNvbHVtbiIsIGhjYWVzKHggPSBSYWNlLCB5ID0gbiwgZ3JvdXAgPSBGaWxtKSkNCmBgYA0KDQojIEEgc2Vjb25kIGBoY2hhcnRgDQpgYGB7ciBoY2hhcnQyLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gOX0NCmhjaGFydChtaWR3ZXN0LCAic2NhdHRlciIsIGhjYWVzKHggPSBwb3BkZW5zaXR5LCB5ID0gcGVyY29sbGVnZSwgZ3JvdXAgPSBzdGF0ZSkpDQpgYGANCg0KIyBIaXN0b2dyYW0NCmBgYHtyIGhjaGFydF9oaXN0LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gOX0NCmhjaGFydChsb3RyJFdvcmRzKQ0KYGBgDQoNCiMgWW91ciBUdXJuDQoxLiBVc2luZyB0aGUgYGhjaGFydGAgZnVuY3Rpb24sIGNyZWF0ZSBhIGJhciBjaGFydCBvciBoaXN0b2dyYW0gd2l0aCB0aGUgYGdzc19jYXRgIGRhdGEuDQoyLiBVc2luZyB0aGUgYGhjaGFydGAgZnVuY3Rpb24sIGNyZWF0ZSBhIHNjYXR0ZXJwbG90IHdpdGggdGhlIGBnc3NfY2F0YCBkYXRhLg0KDQojIEJ1aWxkIEhpZ2hjaGFydHMgZnJvbSBzY3JhdGNoDQpgYGB7ciBoY19zY3JhdGNoLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNn0NCmhjIDwtIGhpZ2hjaGFydCgpICU+JQ0KICBoY194QXhpcyhjYXRlZ29yaWVzID0gbG90cl9jb3VudCRSYWNlKSAlPiUNCiAgaGNfYWRkX3NlcmllcyhuYW1lID0gJ1RoZSBGZWxsb3dzaGlwIE9mIFRoZSBSaW5nJywgDQogICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihsb3RyX2NvdW50LCBGaWxtID09ICdUaGUgRmVsbG93c2hpcCBPZiBUaGUgUmluZycpJG4pICU+JSANCiAgaGNfYWRkX3NlcmllcyhuYW1lID0gJ1RoZSBUd28gVG93ZXJzJywgDQogICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihsb3RyX2NvdW50LCBGaWxtID09ICdUaGUgVHdvIFRvd2VycycpJG4pICU+JQ0KICBoY19hZGRfc2VyaWVzKG5hbWUgPSAnVGhlIFJldHVybiBPZiBUaGUgS2luZycsIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBmaWx0ZXIobG90cl9jb3VudCwgRmlsbSA9PSAnVGhlIFJldHVybiBPZiBUaGUgS2luZycpJG4pDQpoYw0KYGBgDQoNCiMgQ2hhbmdlIENoYXJ0IHR5cGUNCmBgYHtyIGhjX2NoYXJ0LCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNn0NCmhjIDwtIGhjICU+JQ0KICBoY19jaGFydCh0eXBlID0gJ2NvbHVtbicpDQpoYw0KYGBgDQoNCiMgQ2hhbmdlIENvbG9ycw0KYGBge3IgaGNfY2hhbmdlX2NvbG9ycywgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDZ9DQpoYyA8LSBoYyAlPiUNCiAgaGNfY29sb3JzKHN1YnN0cih2aXJpZGlzKDMpLCAwLCA3KSkNCmhjDQpgYGANCg0KIyBNb2RpZnkgQXhlcw0KYGBge3IgaGNfYXhpcywgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDl9DQpoYyA8LSBoYyAlPiUNCiAgaGNfeEF4aXModGl0bGUgPSBsaXN0KHRleHQgPSAiUmFjZSIpKSAlPiUNCiAgaGNfeUF4aXModGl0bGUgPSBsaXN0KHRleHQgPSAiTnVtYmVyIG9mIFdvcmRzIFNwb2tlbiIpLA0KICAgICAgICAgICBzaG93TGFzdExhYmVsID0gRkFMU0UpDQpoYw0KYGBgDQoNCiMgQWRkIHRpdGxlLCBzdWJ0aXRsZSwgbW92ZSBsZWdlbmQNCmBgYHtyIGhjX21vZGlmeSwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDl9DQpoYyA8LSBoYyAlPiUNCiAgaGNfdGl0bGUodGV4dCA9ICdOdW1iZXIgb2YgV29yZHMgU3Bva2VuIGluIExvcmQgb2YgdGhlIFJpbmdzIEZpbG1zJywNCiAgICAgICAgICAgYWxpZ24gPSAnbGVmdCcpICU+JQ0KICBoY19zdWJ0aXRsZSh0ZXh0ID0gJ0Jyb2tlbiBkb3duIGJ5IDxpPkZpbG08L2k+IGFuZCA8Yj5SYWNlPC9iPicsIA0KICAgICAgICAgICAgICBhbGlnbiA9ICdsZWZ0JykgJT4lDQogIGhjX2xlZ2VuZChhbGlnbiA9ICdyaWdodCcsIHZlcnRpY2FsQWxpZ24gPSAndG9wJywgbGF5b3V0ID0gJ3ZlcnRpY2FsJywNCiAgICAgICAgICAgIHggPSAwLCB5ID0gODApICU+JQ0KICBoY19leHBvcnRpbmcoZW5hYmxlZCA9IFRSVUUpDQpoYw0KYGBgDQoNCg0KIyBZb3VyIFR1cm4NCjEuIEJ1aWxkIHVwIGEgcGxvdCBmcm9tIHNjcmF0Y2gsIGdldHRpbmcgdGhlIGZpZ3VyZSBjbG9zZSB0byBwdWJsaWNhdGlvbiBxdWFsaXR5IHVzaW5nIHRoZSBgZ3NzX2NhdGAgZGF0YS4NCg0KIyBDb3JyZWxhdGlvbiBNYXRyaWNlcw0KYGBge3IgY29ycmVsYXRpb259DQpzZWxlY3Qoc3Rvcm1zLCB3aW5kLCBwcmVzc3VyZSwgdHNfZGlhbWV0ZXIsIGh1X2RpYW1ldGVyKSAlPiUNCiAgY29yKHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSAlPiUNCiAgaGNoYXJ0KCkNCmBgYA0KDQojIExlYWZsZXQgRXhhbXBsZQ0KYGBge3IgbGVhZmxldH0NCmxpYnJhcnkobGVhZmxldCkNCg0Kc3Rvcm1zICU+JQ0KICBmaWx0ZXIobmFtZSAlaW4lIGMoJ0lrZScsICdLYXRyaW5hJyksIHllYXIgPiAyMDAwKSAlPiUNCiAgbGVhZmxldCgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRDaXJjbGVzKGxuZyA9IH5sb25nLCBsYXQgPSB+bGF0LCBwb3B1cCA9IH5uYW1lLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIHJhZGl1cyA9IH53aW5kKjEwMDApDQpgYGANCg0KDQojIEFkZGl0aW9uYWwgUmVzb3VyY2VzDQoqIHBsb3RseSBmb3IgUiBib29rOiA8aHR0cHM6Ly9wbG90bHktYm9vay5jcHNpZXZlcnQubWUvPg0KKiBwbG90bHk6IDxodHRwczovL3Bsb3QubHkvPg0KKiBoaWdoY2hhcnRlcjogPGh0dHA6Ly9qa3Vuc3QuY29tL2hpZ2hjaGFydGVyL2luZGV4Lmh0bWw+DQoqIGhpZ2hjaGFydHM6IDxodHRwczovL3d3dy5oaWdoY2hhcnRzLmNvbS8+DQoqIGh0bWx3aWRnZXRzOiA8aHR0cHM6Ly93d3cuaHRtbHdpZGdldHMub3JnLz4NCg==